Auteur : Craig Ringer
L'extension Python de Scribus offre des caractéristiques additionnelles permettant de doter l'application de nouvelles fonctionnalités,plutôt que d'automatiser des tâches. En particulier, il est possible d'utiliser des 'scripts d'extension' pour créer de nouvelles palettes et des fenêtres encastrables qui peuvent être utilisées comme si elles faisaient partie de Scribus.
Les scripts d'extension ressemblent beaucoup aux scripts Python normaux de Scribus. Ils comportent quelques variantes pour pouvoir être lancés avec la caractéristique "script d'extension", qui leur donne accès au support PyQt et à d'autres caractéristiques spéciales du scripteur. Les différences les plus importantes entre les scripts normaux et les scripts d'extensions sont les suiantes :
Les scripts normaux sont lancés dans un nouveau sous-interpréteur Python qui est utilisé exclusivement pour ce script et se trouve ensuite libéré. Cela signifie que, quels que soient les objets Python qu'ils créent ou les paramètres qu'ils modifient, ces changements sont automatiquement annulés lorsque le script se termine. Parce que Scribus prend soin de libérer votre script, vous n'avez pas à vous préoccuper de la mémoire ni des conflits avec d'autres scripts, etc., et vous pouvez vous concentrer sur l'écriture des instructions.
Les scripts d'extension, par contre, s'exécutent dans un seul interpréteur Python qui démarre au chargement du module de gestion des extensions. Cet
interpréteur continue à tourner jusqu'à ce que le script qui gère les extensions soit désactivé à la fermeture de Scribus. Quand Scribus exécute un script d'extension, il le charge dans l'interpréteur courant - un peu comme execfile
charge un script Python dans un autre script Python. Ainsi, les scripts d'extension peuvent créer de nouveaux objets au cours de leur exécution puis terminer et redonner le contrôle à Scribus sans que objets qu'ils ont créés ne soient détruits. Si un autre script s'exécute alors, il peut voir les objets créés par le premier script.
Il y a plusieurs situations où il est utile de créer des objets Python qui subsistent après le script. Le cas le plus pertinent réside dans la programmation graphique avec PyQt, où les objets PyQt sont créés au démarrage du script et ne deviennent opérationnels que lorsque le script termine, retournant le contrôle à la boucle événementielle de Scribus. Autres cas de figure : les macros, les événements de rappel et les temporisateurs, où Scribus doit pouvoir appeler le code Python. Vous pouvez réaliser ces fonctions dès maintenant avec PyQt, mais il n'existe pas encore de support direct pour les temporisateurs et les rappels dans Scribus.
Voici quelques inconvénients des objets persistants après la fin du script. Les scripts interagissent parfois d'une manière imprévue par l'auteur, ce qui donne des résultats souvent intéressants mais cause aussi des bogues inattendus et étonnants. Les auteurs de scripts doivent par ailleurs considérer l'effet de leur code sur la consommation de la mémoire de Scribus.
Construire de nouvelles palettes et dialogues dans PyQt constitue un moyen simple d'enrichir l'interface utilisateur de Scribus et de fournir des fonctionnalités supplémentaires pour les scripts avancés. Python est bien adapté aux entrées et sorties à partir de bases de données, de systèmes de gestion de contenu et d'autres référentiels externes; la capacité de bâtir des interfaces personnalisées à cette fin peut s'avérer très utile.
Dans la plupart des cas, PyQt fonctionne de la même manière à l'intérieur de Scribus ou dans un interpréteur Python autonome. Il y a cependant des différences, et il est important de les comprendre.
QApplication
existe déjà, et le fait d'en créer une autre aura des conséquences indésirables. Au besoin, vous pouvez
accéder à l'instance de QApplication
en tant que qt.qApp
. Le premier tutoriel est l'application classique Hello World. Pour montrer les différences entre PyQt et Scribus, nous convertirons le programme pour qu'il s'exécute dans Scribus. Voici l'original :
#!/usr/bin/env python import sys import qt a = qt.QApplication(sys.argv) hello = qt.QPushButton("Hello world!", None) hello.resize(100, 30) a.setMainWidget(hello) hello.show() sys.exit(a.exec_loop())
Premièrement, nous avons besoin de désactiver la création de QApplication
puisque, dans Scribus, on trouve une instance de QApplication
déjà active; rappelez-vous qu'une seule instance est permise par application. PyQt nous fournit l'accès à QApplication
, créé préalablement par Scribus au démarrage en tant que qt.qApp
. Donc, il suffit de remplacer :
a = qt.QApplication(sys.argv)
par
a = qt.qApp
et nous avons terminé la modification.
Ensuite, nous devons empêcher le script d'essayer d'exécuter sa propre boucle d'événements. Comme Scribus possède une boucle d'événements, si le script démarre la sienne, l'application sera perturbée jusqu'à sa fermeture. Qt est assez futé pour associer toute fenêtre que vous créez à la boucle d'événements exsitante; il n'y a donc pas grand chose à faire. Pendant l'exécution du script, Scribus est sous le contrôle de Python, de sorte qu'il nous suffit de réaliser notre installation (dans ce cas, il s'agit de créer une fenêtre simple et de l'afficher), pour ensuite laisser notre script se terminer plutôt que de déclencher la boucle d'événements. Tous les scripts d'extension s'exécutent dans le même interpréteur Python, ce qui implique que les objets créés par vos soins ne sont pas détruits à la fin de l'exécution. C'est un peu comme charger un module. Quand votre script se termine, Scribus prend le contrôle et reprend l'exécution de la boucle d'événements de Qt. Comme vos fenêtres sont des widgets Qt, la boucle d'événements de Scribus les prend en charge, et elles deviennent une partie intégrante normale de Scribus. Quand une insertion Python est déclenchée ou qu'une fonction Python est appelée, PyQt se charge automatiquement de l'exécution de la fonction Python et passe ensuite la main à Scribus.
Le seul hic de cette méthode est qu'à la fin de votre script, tous les objets créés dans une fonction ou un domaine local seront libérés par Python lorsque le domaine est abandonné (par exemple à la sortie de main()). Vous devez conserver une référence au niveau global pour éviter que ces éléments ne soient libérés. Le support pour PyQt dans Scribus est très récent et il n'y a pas encore de méthode clairement définie comme "correcte" d'accomplir cela. Les options incluent :
Pour l'instant, parce que ce script crée déjà tous les éléments au niveau global, nous allons procéder comme cela. Les scripts volumineux devraient être écrits comme des modules.
Étant donné que les objets dont nous avons besoin seront déjà présents lorsque le script se terminera, il nous suffit d'empêcher l'entrée dans la boucle d'événements. C'est facile - mettez simplement en commentaire la dernière ligne :
# sys.exit(a.exec_loop())
et nous avons presque terminé. Le script s'exécutera maintenant correctement, mais, à la fermeture, il aura un effet non désiré -l'interruption de Scribus. Ce n'est probablement pas ce que vous voulez. Voilà l'explication : normalement, une application Qt se termine lorsque son widget principal (fenêtre principale) se ferme. Nous
appelons qt.setMainWidget(...)
pour transformer notre nouvelle fenêtre en fenêtre principale; donc, à la fermeture, Scribus s'interrompt aussi. Pour empêcher cela, mettez simplement en commentaire qt.setMainWidget
.
Le nouveau script ressemble à ceci :
#!/usr/bin/env python import sys import qt a = qt.qApp hello = qt.QPushButton("Hello world!", None) hello.resize(100, 30) #a.setMainWidget(hello) hello.show() #sys.exit(a.exec_loop())
Vous trouverez le script déjà enregistré sous le nom pyqt_tut1.py
dans le répertoire 'examples' du scripteur. Essayez de l'exécuter comme un script d'extension. Vous devriez obtenir un bouton 'hello world'. Notez que vous pouvez continuer à travailler dans Scribus comme à l'accoutumée; lorsque vous fermez la fenêtre 'hello world', elle disparaît élégamment, sans incidence sur Scribus.
Si vous jetez un coup d'oeil à l'exemple de ce script tutoriel, vous remarquerez quelques ajouts. Ils sont accompagnés de commentaires explicatifs et ne seront donc pas explorés plus avant ici.
Vous vous rappelez que j'ai mentionné plus tôt certains 'problèmes' concernant le stockage d'objets à conserver au niveauglobal ? Évidemment, j'ai passé sous silence quelque chose que je ne voulais pas expliquer à ce moment.
Le stockage d'objets en tant que noms globaux fonctionne bien... jusqu'à ce que l'utilisateur exécute votre script à nouveau, ou qu'il exécute un autre script qui reprend les mêmes noms. Python utilise le comptage de références : un objet continue d'exister tant qu'un ou plusieurs noms y font référence. Lorsqu'un nom global créé antérieurement est remplacé par un autre script ou par une autre exécution de votre script, il n'y a plus de référence à cet objet (peut-être une fenêtre que l'utilisateur continue d'utiliser). Python fait son travail scrupuleusement et supprime l'objet pour vous, sans savoir s'il est encore affiché ou s'il s'agit d'un composant de votre fenêtre. Dans beaucoup de cas, une fenêtre disparaît tout simplement, mais il peut y avoir des conséquences plus ennuyeuses.
Essayez ceci. Exécutez le script 'hello world' (en utilisant "Load Extension Script..."). Puis, sans fermer la fenêtre "Hello world", exécutez le script à nouveau. La fenêtre originale devrait disparaître et être remplacée par la nouvelle.
Aucune solution idéale n'est prévue pour ce problème, et tout dépend de ce que vous voulez faire exactement. J'aimerais donner des recommandations plus claires, mais chaque cas est différent. Si vous recontrez ce problème, entrez une description de votre projet sur la liste de diffusion de Scribus, et mes collègues ou moi-même vous fourniront quelques suggestions.
La meilleure solution jusqu'à présent consiste à utiliser un script enveloppe pour exécuter votre script et de placer votre script réel dans un module. Le script enveloppe importe votre module et exécute une fonction du module pour afficher les fenêtres. Puisque le module est exécuté uniquement la première fois qu'il est importé, la(les) fenêtre(s) seront affichées si elles ne sont pas déjà visibles, mais ne seront pas perturbées si elles sont déjà affichées. Vous pouvez appeler reload() pour recharger le module si vous voulez vraiment le relancer, peut-être après l'exécution de code de nettoyage.
Les suggestions appropriées sont bienvenues. N'hésitez pas à entrer vos questions et idées sur la liste de diffusion.
Même si vous ne bâtissez pas une interface graphique personnalisée, il est possible d'utiliser les scripts d'extension. Par exemple, vous pouvez utiliser PyQt pour exécuter une fonction temporisée. Un autre usage pourrait être de vérifier l'existence de mises à jour d'un article dans une base de données et d'inviter l'utilisateur à actualiser son document avec le nouveau texte (ou à constater les différences). Vous
trouverez un exemple très simple de mise en place d'un temporisateur
avec PyQt dans le répertoire d'exemples, appelé pyqt_timer.py
.
Une autre idée, suggérée par un membre de la liste de diffusion, était d'écrire un serveur XML-RPC pour exposer le scripteur API à des programmes externes. Cette approche pourrait être réalisée à l'aide des classes PyQt pour la mise en réseau et la gestion des événements.
Le présent document n'est pas un tutoriel PyQt ou Qt. Voici quelques sources d'information fiables sur Qt et PyQt :
Essayer ceci: import scribus except ImportError: print "Ce script peut seulement s'exécuter comme un script d'extension à partir de Scribus." sys.exit(1)
Cette séquence essaie de charger l'interface de script de Scribus et, en cas
d'échec, suppose que celle-ci ne peut s'exécuter sous Scribus. Veillez à placer ce message dans tous vos scripts pour ne pas
dérouter les utilisateurs qui essaient de les exécuter avec
l'interpréteur Python autonome. Essayez d'exécuter le script avec
python pyqt_tut1.py
, et notez comment il signale son échec avant de fermer. C'est
beaucoup plus précis qu'une erreur d'importation ou qu'un comportement bizarre.
Le support pour étendre Scribus à partir de Python est encore en chantier. Plusieurs composants fonctionnent bien, mais il reste beaucoup à explorer. Les réactions, suggestions, requêtes, idées et offres d'aide seront grandement appréciées et peuvent être dirigées vers la liste de diffusion ou vers les auteurs du présent document.
Notamment, il n'existe aucun support pour :
Certains de ces éléments ne sont pas encore implémentés, d'autres sont extrêmement difficiles à gérer; dans d'autres cas, nous sommes tout simplement dépourvus d'idées ou ne prévoyons pas d'essayer.